<template>
  <div ref="canvas"></div>
</template>

<script lang="ts" setup>
import * as THREE from "three";
import {OrbitControls} from "three/examples/jsm/controls/OrbitControls";
import {GUI} from "three/examples/jsm/libs/lil-gui.module.min.js";
import Stats from "three/examples/jsm/libs/stats.module";
import {onMounted, ref} from "vue";

// 画布
const canvas = ref<any>('');
// 场景
const scene = new THREE.Scene();
// 相机
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(-20, 25, 20);
camera.lookAt(5, 0, 0);
// 平面
const planeGeometry = new THREE.PlaneGeometry(60, 40, 1, 1);
const planeMaterial = new THREE.MeshLambertMaterial({
  color: 0xffffff,
  side: THREE.DoubleSide,
});
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.rotation.x = -0.5 * Math.PI;
plane.position.set(0, 0, 0);
plane.receiveShadow = true;
scene.add(plane);
// 添加自定义几何体
// 几何体的顶点 - 顶点顺序决定了某个面是面向摄像机还是背向摄像机的。如果你想创建面向摄像机的面，那么顶点的顺序是顺时针的，反之顶点的顺序是逆时针的
// 三个一组，一组代表一个顶点
const vertices = new Float32Array([
  1, 3, 1,
  1, 3, -1,
  1, -1, 1,
  1, -1, -1,
  -1, 3, -1,
  -1, 3, 1,
  -1, -1, -1,
  -1, -1, 1
]);
// 索引 - 三个一组，一组代表一个三角形.
// 一个数字代表一个顶点: 0 代表 vertices 中的 (1, 3, 1)
//                   2 代表 vertices 中的 (1, 3, -1)
//                   1 代表 vertices 中的 (1, -1, 1)
const idx = new Uint16Array([
  0, 2, 1,
  2, 3, 1,
  4, 6, 5,
  6, 7, 5,
  4, 5, 1,
  5, 0, 1,
  7, 6, 2,
  6, 3, 2,
  5, 7, 0,
  7, 2, 0,
  1, 3, 4,
  3, 6, 4,
]);
const geometry = new THREE.BufferGeometry();
const attributes = new THREE.BufferAttribute(vertices, 3);
geometry.attributes.position = attributes;
geometry.index = new THREE.BufferAttribute(idx, 1);

const materials = [
  new THREE.MeshBasicMaterial({ color: 0x000000, wireframe: true, side: THREE.DoubleSide, }),
  new THREE.MeshLambertMaterial({ color: 0x44ff44, opacity: 0.6, transparent: true, side: THREE.DoubleSide, })
];

// 为 同一个几何体，在不同材质下都创建一个Mesh实例，并将这些Mesh实例放入到同一个组中
const cube = new THREE.Group();
for (let k = 0; k < materials.length; k++) {
  cube.add(new THREE.Mesh(geometry, materials[k]));
}
cube.castShadow = true;
cube.children.forEach( (e) => {
  e.castShadow = true;
});
scene.add(cube);

// 光源
const ambientLight = new THREE.AmbientLight(0x494949);
const spotLight = new THREE.SpotLight(0xffffff, 1, 180, Math.PI / 4);
spotLight.position.set(-40, 30, 30);
spotLight.castShadow = true;
spotLight.shadow.mapSize = new THREE.Vector2(window.innerWidth, window.innerHeight);
// spotLight.shadow.camera.far = 130;
// spotLight.shadow.camera.near = 40;
spotLight.lookAt(cube.position);
scene.add(ambientLight, spotLight);
// 渲染器
const renderer = new THREE.WebGLRenderer({
  antialias: true,
});
renderer.shadowMap.enabled = true;
renderer.setClearColor(0x000000);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
// 相机控件
const orbitControl = new OrbitControls(camera, renderer.domElement);
orbitControl.update();
// 性能统计
const stat = Stats();
stat.setMode(0);
// gui界面
const gui = new GUI();
/**
 * 生成控制点
 */
const createControlPoint = (x, y, z) => {
  return new function () {
    this.x = x;
    this.y = y;
    this.z = z;
  }
}
// 控制点集合
const controlPoints = [];
controlPoints.push(createControlPoint(3, 5, 3));
controlPoints.push(createControlPoint(3, 5, 0));
controlPoints.push(createControlPoint(3, 0, 3));
controlPoints.push(createControlPoint(3, 0, 0));
controlPoints.push(createControlPoint(0, 5, 0));
controlPoints.push(createControlPoint(0, 5, 3));
controlPoints.push(createControlPoint(0, 0, 0));
controlPoints.push(createControlPoint(0, 0, 3));
// 控制点GUI界面
for (let i = 0; i < controlPoints.length; i++) {
  const folder = gui.addFolder('Vertices' + (i + 1));
  // @ts-ignore
  folder.add(controlPoints[i], 'x', -10, 10).name("顶点" + (i + 1) + "X轴");
  // @ts-ignore
  folder.add(controlPoints[i], 'y', -10, 10).name("顶点" + (i + 1) + "Y轴");
  // @ts-ignore
  folder.add(controlPoints[i], 'z', -10, 10).name("顶点" + (i + 1) + "Z轴");
}
// 克隆几何体
// @ts-ignore
gui.add({ clone: () => {
    const clonedGeometry = geometry.clone();
    const materials = [
      new THREE.MeshLambertMaterial({ opacity: 0.8, color: 0xff44ff, transparent: true, side: THREE.DoubleSide, }),
      new THREE.MeshBasicMaterial({ color: 0x000000, wireframe: true, side: THREE.DoubleSide, })
    ];
    const group = new THREE.Group();
    for (let k = 0; k < materials.length; k++) {
      group.add(new THREE.Mesh(clonedGeometry, materials[k]));
    }
    group.castShadow = true;
    group.children.forEach( (e) => {
      e.castShadow = true;
    });

    group.translateX(5);
    group.translateZ(5);
    group.name = "cloned";

    if (scene.getObjectByName("cloned")) {
      scene.remove(scene.getObjectByName("cloned"));
    }
    scene.add(group);
  } }, 'clone').name("克隆");

/**
 * 渲染函数
 */
const render = () => {
  stat.update();

  // 根据GUI界面的控制点调整结果，重新渲染几何体
  const vertices = [];
  const len = controlPoints.length;
  for (let i = 0; i < len; i++) {
    const cp = controlPoints[i];
    vertices.push(cp.x, cp.y, cp.z);
  }
  cube.children.forEach((e) => {
    if (e instanceof THREE.Mesh) {
      if (e.geometry) {
        e.geometry.attributes.position = new THREE.BufferAttribute(new Float32Array(vertices), 3);
        e.geometry.index = new THREE.BufferAttribute(idx, 1);
        e.geometry.computeVertexNormals();
      }
    }
  });

  renderer.render(scene, camera);
  requestAnimationFrame(render);
}

onMounted(() => {
  canvas.value.appendChild(renderer.domElement);
  canvas.value.appendChild(stat.domElement);
  render();
})
</script>

<style scoped>
body{
  margin: 0;
  overflow: hidden;
}
</style>
